#include"acceptor.h"

#include<assert.h>
#include<cstring>
#include<sys/socket.h>
#include<sys/types.h>
#include<bits/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<netinet/tcp.h>
#include<functional>

#include"address.h"
#include"channel.h"

using namespace tiny_muduo;

Acceptor::Acceptor(EventLoop* loop,const Address& address)
    :loop_(loop),
     listenfd_(::socket(PF_INET,SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC,IPPROTO_TCP)),
     idlefd_(::open("dev/null",O_RDONLY|O_CLOEXEC)),
     channel_(new Channel(loop,listenfd_)){
        SetSockoptReuseAddr(listenfd_);
        SetSockoptKeepAlive(listenfd_);
        BindListenFd(address);
        channel_->SetReadCallback(std::bind(&Acceptor::NewConnection,this));
     }

Acceptor::~Acceptor(){
    channel_->DisableAll();
    loop_->Remove(channel_.get());
    ::close(listenfd_);
}


void Acceptor::BindListenFd(const Address& addr){
    struct sockaddr_in address;
    memset(&address,0,sizeof(address));
    address.sin_family=AF_INET;
    address.sin_addr.s_addr=htonl(INADDR_ANY);
    address.sin_port=htons(addr.port());
    int ret=bind(listenfd_,(struct sockaddr*)(&address),sizeof(address));
    assert(ret!=-1);
    (void)ret;
}

void Acceptor::Listen(){
    int ret=listen(listenfd_,SOMAXCONN);
    assert(ret!=-1);
    (void)ret; //如果assert没用到，ret会被编译器警告未使用的变量，为了消除警告加这样一行代码
    channel_->EnableReading();
}

void Acceptor::NewConnection(){
    struct sockaddr_in client;
    socklen_t client_addrlength=sizeof(client);
    int connfd=::accept4(listenfd_,(struct sockaddr*)&client,&client_addrlength,SOCK_NONBLOCK|SOCK_CLOEXEC);
//accept4相比accept的优点就是多了一个参数flags，可以设置SOCK_NONBLOCK、SOCK_CLOEXEC等参数，最大的好处就是可以直接把socket设置为非阻塞，不然还要自己写函数实现socket的非阻塞
    if(connfd<0){
        if(errno==EMFILE){//这个错误是指已达到可用文件描述符的最大数量
            ::close(idlefd_);
            idlefd_=::accept(listenfd_,NULL,NULL);
            ::close(idlefd_);
            idlefd_=::open("/dev/null",O_RDONLY|O_CLOEXEC);
        }
        return;
    }
    assert(connfd>0);
    if(SetSockoptKeepAlive(connfd)==-1){
        printf("Acceptor::NewConnection SetSockoptKeepAlive failed\n");
        close(connfd);
        return;
    }
    if(SetSockoptTcpNoDelay(connfd)==-1){
        printf("Acceptor::NewConnection SetSockoptTcpNoDelay failed\n");
        close(connfd);
        return;
    }
    new_connection_callback_(connfd);
}